iT邦幫忙

2022 iThome 鐵人賽

DAY 22
2
Modern Web

.NET教我做人系列 第 22

Day22 JWT要怎麼使用

  • 分享至 

  • xImage
  •  

昨天我們講完了JWT 的主要架構後,今天的重點主要在ASP.NET Core 專案實現 Token-based 的身分驗證與授權,最簡單的方式就是透過 JWT 進行實作,主要有三大重點,產生有效Token、驗證Token、對特硬API作授權設定

首先的話,我們要先去安裝Microsoft.AspNetCore.Authentication.JwtBearer套件

appsettings.json裡加入我們JWT 一些組態設定

"JwtSettings": {
      "Issuer": "IMAC",
      "SignKey": "bJs3iqzDSP1qiTzWeMJa2cMsQFji2q6DL5exm0wVKo21NczRvpfE5m7oUE1VCp4F",
      "ExpireMinutes": 720
    }

接著我們去創一個檔案Helpers/JwtHelper.cs,這檔案主要是來寫JWT 所需要的設定

using System.Security.Claims;
using System.Text;
using System.IdentityModel.Tokens.Jwt;
using Microsoft.IdentityModel.Tokens;

public class JwtHelpers
{
    private readonly IConfiguration Configuration;

    public JwtHelpers(IConfiguration configuration)
    {
        this.Configuration = configuration;
    }

    public string GenerateToken(string userName, int expireMinutes = 30)
    {
        var issuer = Configuration.GetValue<string>("JwtSettings:Issuer");
        var signKey = Configuration.GetValue<string>("JwtSettings:SignKey");

        var claims = new List<Claim>();

        
        claims.Add(new Claim(JwtRegisteredClaimNames.Sub, userName));
        // Token 的主體內容
        claims.Add(new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString())); 
        // JWT ID
        //claims.Add(new Claim(JwtRegisteredClaimNames.Iss, issuer));
        //claims.Add(new Claim(JwtRegisteredClaimNames.Aud, "The Audience"));
        //claims.Add(new Claim(JwtRegisteredClaimNames.Exp, DateTimeOffset.UtcNow.AddMinutes(30).ToUnixTimeSeconds().ToString()));
        //claims.Add(new Claim(JwtRegisteredClaimNames.Nbf, DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString()));
        //claims.Add(new Claim(JwtRegisteredClaimNames.Iat, DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString()));

        claims.Add(new Claim("roles", "Admin"));
        claims.Add(new Claim("roles", "Users"));

        var userClaimsIdentity = new ClaimsIdentity(claims);

        // 建立一組對稱式加密的金鑰,主要用於 JWT 簽章之用
        var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(signKey));

        // HmacSha256 MUST be larger than 128 bits, so the key can't be too short. At least 16 and more characters.
        var signingCredentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256Signature);

        // Create SecurityTokenDescriptor
        var tokenDescriptor = new SecurityTokenDescriptor
        {
            Issuer = issuer,
            //Audience = issuer, 
            // 由於你的 API 受眾通常沒有區分特別對象,因此通常不太需要設定,也不太需要驗證
            //NotBefore = DateTime.Now, 預設值就是 DateTime.Now
            //IssuedAt = DateTime.Now, 預設值就是 DateTime.Now
            Subject = userClaimsIdentity,
            Expires = DateTime.Now.AddMinutes(expireMinutes),
            SigningCredentials = signingCredentials
        };

        // 產出所需要的 JWT securityToken 物件,並取得序列化後的 Token 結果(字串格式)
        var tokenHandler = new JwtSecurityTokenHandler();
        var securityToken = tokenHandler.CreateToken(tokenDescriptor);
        var serializeToken = tokenHandler.WriteToken(securityToken);

        return serializeToken;
    }
}

這部分可以說就是昨天Payload部分,而這裡是依 RFC 7519 的規格來寫,它總共定義了 7 個預設的 Claims

claims.Add(new Claim(JwtRegisteredClaimNames.Sub, userName));
claims.Add(new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString())); 
claims.Add(new Claim(JwtRegisteredClaimNames.Iss, issuer));
claims.Add(new Claim(JwtRegisteredClaimNames.Aud, "The Audience"));
...

再來這部分是可以自行擴充使用者身分,並使用roles 加入

claims.Add(new Claim("roles", "Admin"));
claims.Add(new Claim("roles", "Users"));

驗證合法有效的 JWT Token

這部分是要讓你的 ASP.NET Core 能夠認得使用者傳入的 Bearer Token

builder.Services
    .AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer(options =>
    {
        options.IncludeErrorDetails = true; // 當驗證失敗時,會顯示失敗的詳細錯誤原因

        options.TokenValidationParameters = new TokenValidationParameters
        {
            // 簽發者
            ValidateIssuer = true,
            ValidIssuer = builder.Configuration.GetValue<string>("JwtSettings:Issuer"),
            // 接收者
            ValidateAudience = false,
            ValidAudience = "JwtAuthDemo",
            // Token 的有效期間
            ValidateLifetime = true,
            // 如果 Token 中包含 key 才需要驗證,一般都只有簽章而已
            ValidateIssuerSigningKey = false,
            // key
            IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(builder.Configuration.GetValue<string>("JwtSettings:SignKey")))
        };
    });

記得還要記得加上中介軟體AddAuthorization() ,一定要放在UseAuthorization上面,因為要記得使用者要先驗證過才會授權

JWT 使用

接這我們去新增一個新的控制器JwtController.cs

這裡我們在最上方加上[Authorize],是因為我們需要使用者有驗證過才能使用到特定的API
接著在~/gentoken這API上加入[AllowAnonymous]是讓每位使用者不用先驗證正就可以產Token,不然前面要先驗證才能產Token有矛盾

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;

namespace DB_CRUD.Controllers
{
    
    [Authorize]
    [Route("api/[controller]")]
    [ApiController]
    public class JwtController : ControllerBase
    {
        private readonly JwtHelpers _jwtHelpers;

        public JwtController(JwtHelpers jwtHelpers)
        {
            _jwtHelpers = jwtHelpers;
        }


        // 這裡用來產生我們的Token
        [AllowAnonymous]
        [HttpPost("~/gentoken")]
        public IActionResult GenToken(string username)
        {
            if (string.IsNullOrEmpty(username))
            {
                return BadRequest();
            }
            var token = _jwtHelpers.GenerateToken(username);
            return Ok(token);
        }

        // 來看看我們在Claims裡有有哪些屬性和內容
        [HttpGet("~/claims")]
        public IActionResult GetClaims()
        {
            return Ok(User.Claims.Select(p => new { p.Type, p.Value }));
        }

        // 回傳我們剛剛在產Token時輸入的username
        [HttpGet("~/username")]
        public IActionResult GetUserName()
        {
            return Ok(User.Identity.Name);
        }

        // 傳回Jwt的id
        [HttpGet("~/jwtid")]
        public IActionResult GetUniqueId()
        {
            var jti = User.Claims.FirstOrDefault(p => p.Type == "jti");
            return Ok(jti.Value);
        }
    }
}

重要提醒,記得要在註冊剛才寫的JwtHelper,不然你到死也不會成功

builder.Services.AddSingleton<JwtHelpers>();

怎麼驗證JWT

話說產Token有了,驗證Token有了,但我們要去做驗證這動作,這裡有兩種方式可以來完成,第一種方式就是使用Postman裡的Auth機制來幫我們驗證

Postman

先去產Token

Auth 選擇驗證方式和加入剛才的Token,沒錯誤的話結果就會呈現出來

swagger

要是現在我們不是用Postman 來做驗證是使用swagger的話要現到Program.cs的AddSwaggerGen去做更動

builder.Services.AddSwaggerGen(options => {
    options.AddSecurityDefinition("BearerAuth", new OpenApiSecurityScheme
    {
        Type = SecuritySchemeType.Http,
        Scheme = "bearer",
        BearerFormat = "JWT",
        Description = "JWT Authorization header using the Bearer scheme."
    });
    options.AddSecurityRequirement(new OpenApiSecurityRequirement 
    {
        {
            new OpenApiSecurityScheme
            {
                Reference = new OpenApiReference { Type = ReferenceType.SecurityScheme, Id = "BearerAuth"}
            },
            new string[] { }
        }
    });
});

這裡就不多做說明,大家可以到連結去看看 link

接下來一樣先去產Token

載來你會看到跟之前的swagger不一樣,多了一個Authorize的按鈕,它就是來幫我們做驗證的東西

如果有輸入Token 後應該會發現旁邊的鎖會變成鎖起來的樣子,但這裡不一定代表驗證是正確,還是要依你API 去跑的狀況來看

今天花了好一大段時間在整理JWT ,希望大家能看得懂我所寫的鐵人賽,哪今天就希望大家連假愉快~~~

參考資料:


上一篇
Day21 JWT身分認證
下一篇
Day23 DTO是甚麼
系列文
.NET教我做人30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言